/**
 * 此测量工具，参考 https://openlayers.org/en/latest/examples/measure-style.html
 * 关于此处封装，有以下几点说明：
 * 1. 有较多常量在类外，暂不知如何加入类里，需要时再初始化
 * 2. 测量绘制时的样式待调整，有时会与底图叫接近，无法看清
 */

import { Circle as CircleStyle, Fill, RegularShape, Stroke, Style, Text } from 'ol/style';
import { Draw, Modify } from 'ol/interaction';
import { LineString, Point } from 'ol/geom';
import { Vector as VectorSource } from 'ol/source';
import { Vector as VectorLayer } from 'ol/layer';
import { getArea, getLength } from 'ol/sphere';

const style = new Style({
	fill: new Fill({
		color: 'rgba(255, 255, 255, 0.2)',
	}),
	stroke: new Stroke({
		color: 'rgba(0, 0, 0, 0.5)',
		lineDash: [10, 10],
		width: 2,
	}),
	image: new CircleStyle({
		radius: 5,
		stroke: new Stroke({
			color: 'rgba(0, 0, 0, 0.7)',
		}),
		fill: new Fill({
			color: 'rgba(255, 255, 255, 0.2)',
		}),
	}),
});

const labelStyle = new Style({
	text: new Text({
		font: '14px Calibri,sans-serif',
		fill: new Fill({
			color: 'rgba(255, 255, 255, 1)',
		}),
		backgroundFill: new Fill({
			color: 'rgba(0, 0, 0, 0.7)',
		}),
		padding: [3, 3, 3, 3],
		textBaseline: 'bottom',
		offsetY: -15,
	}),
	image: new RegularShape({
		radius: 8,
		points: 3,
		angle: Math.PI,
		displacement: [0, 10],
		fill: new Fill({
			color: 'rgba(0, 0, 0, 0.7)',
		}),
	}),
});

const tipStyle = new Style({
	text: new Text({
		font: '12px Calibri,sans-serif',
		fill: new Fill({
			color: 'rgba(255, 255, 255, 1)',
		}),
		backgroundFill: new Fill({
			color: 'rgba(0, 0, 0, 0.4)',
		}),
		padding: [2, 2, 2, 2],
		textAlign: 'left',
		offsetX: 15,
	}),
});

const modifyStyle = new Style({
	image: new CircleStyle({
		radius: 5,
		stroke: new Stroke({
			color: 'rgba(0, 0, 0, 0.7)',
		}),
		fill: new Fill({
			color: 'rgba(0, 0, 0, 0.4)',
		}),
	}),
	text: new Text({
		text: 'Drag to modify',
		font: '12px Calibri,sans-serif',
		fill: new Fill({
			color: 'rgba(255, 255, 255, 1)',
		}),
		backgroundFill: new Fill({
			color: 'rgba(0, 0, 0, 0.7)',
		}),
		padding: [2, 2, 2, 2],
		textAlign: 'left',
		offsetX: 15,
	}),
});

const segmentStyle = new Style({
	text: new Text({
		font: '12px Calibri,sans-serif',
		fill: new Fill({
			color: 'rgba(255, 255, 255, 1)',
		}),
		backgroundFill: new Fill({
			color: 'rgba(0, 0, 0, 0.4)',
		}),
		padding: [2, 2, 2, 2],
		textBaseline: 'bottom',
		offsetY: -12,
	}),
	image: new RegularShape({
		radius: 6,
		points: 3,
		angle: Math.PI,
		displacement: [0, 8],
		fill: new Fill({
			color: 'rgba(0, 0, 0, 0.4)',
		}),
	}),
});

const segmentStyles = [segmentStyle];
let modify, tipPoint;

function formatLength (line) {
	const length = getLength(line, { projection: 'EPSG:4326' });
	let output;
	if (length > 100) {
		output = Math.round((length / 1000) * 100) / 100 + ' km';
	} else {
		output = Math.round(length * 100) / 100 + ' m';
	}
	return output;
}

function formatArea (polygon) {
	const area = getArea(polygon, { projection: 'EPSG:4326' });
	let output;
	if (area > 10000) {
		output = Math.round((area / 1000000) * 100) / 100 + ' km\xB2';
	} else {
		output = Math.round(area * 100) / 100 + ' m\xB2';
	}
	return output;
}

function styleFunction (feature, segments = true, drawType, tip) {
	const styles = [style];
	const geometry = feature.getGeometry();
	const type = geometry.getType();
	let point, label, line;
	if (!drawType || drawType === type) {
		if (type === 'Polygon') {
			point = geometry.getInteriorPoint();
			label = formatArea(geometry);
			line = new LineString(geometry.getCoordinates()[0]);
		} else if (type === 'LineString') {
			point = new Point(geometry.getLastCoordinate());
			label = formatLength(geometry);
			line = geometry;
		}
	}
	if (segments && line) {
		let count = 0;
		line.forEachSegment(function (a, b) {
			const segment = new LineString([a, b]);
			const label = formatLength(segment);
			if (segmentStyles.length - 1 < count) {
				segmentStyles.push(segmentStyle.clone());
			}
			const segmentPoint = new Point(segment.getCoordinateAt(0.5));
			segmentStyles[count].setGeometry(segmentPoint);
			segmentStyles[count].getText().setText(label);
			styles.push(segmentStyles[count]);
			count++;
		});
	}
	if (label) {
		labelStyle.setGeometry(point);
		labelStyle.getText().setText(label);
		styles.push(labelStyle);
	}
	if (tip && type === 'Point' && !modify.getOverlay().getSource().getFeatures().length) {
		tipPoint = geometry;
		tipStyle.getText().setText(tip);
		styles.push(tipStyle);
	}
	return styles;
}

class MeasureTool {
	constructor(map) {
		this.map_ = map;
		this.source_ = new VectorSource();
		modify = new Modify({ source: this.source_, style: modifyStyle });
		this.vector_ = new VectorLayer({
			source: this.source_,
			style: function (feature) {
				return styleFunction(feature);
			},
		});
		this.map_.addInteraction(modify);
		this.map_.addLayer(this.vector_);
		this.draw_ = null;
	}

	addInteraction (drawType, isShowSegment = true, isClearPrevious = true) {
		// drawType = 'LineString' | 'Polygon'
		const activeTip = 'Click to continue drawing the ' + (drawType === 'Polygon' ? 'polygon' : 'line');
		const idleTip = 'Click to start measuring';
		let tip = idleTip;
		this.draw_ = new Draw({
			source: this.source_,
			type: drawType,
			style: function (feature) {
				return styleFunction(feature, isShowSegment, drawType, tip);
			},
		});
		this.draw_.on('drawstart', () => {
			if (isClearPrevious) {
				this.source_.clear();
			}
			modify.setActive(false);
			tip = activeTip;
		});
		this.draw_.on('drawend', () => {
			modifyStyle.setGeometry(tipPoint);
			modify.setActive(true);
			this.map_.once('pointermove', () => {
				modifyStyle.setGeometry();
			});
			tip = idleTip;
		});
		modify.setActive(true);
		this.map_.addInteraction(this.draw_);
	}

	toggleMeasureType (drawType) {
		this.map_.removeInteraction(this.draw_);
		this.addInteraction(drawType);
	}

	clearMeasure () {
		this.source_.clear();
	}

	destroyTool () {
		this.map_.removeInteraction(this.draw_);
		this.map_.removeLayer(this.vector_);
		this.map_.removeInteraction(modify);

		this.draw_ = null;
		this.vector_ = null;
		modify = null;
		tipPoint = null;
	}
}

export default MeasureTool;
